package gp;

import firing.BinaryNode;
import firing.ChangeAngleNode;
import firing.EnergyBranch;
import firing.FireNode;
import firing.FiringTree;
import firing.FlipFlopBranch;
import firing.LastFiredBranch;
import firing.Node;
import firing.PlayerLeftRightBranch;
import firing.PlayerUpDownBranch;
import firing.UnaryNode;

public class TreeGenerator {
    
    public static final int DEFAULT_DEPTH = 10;
    public static final int MAXIMUM_SUBTREE_DEPTH = 10;
    public static final int MINIMUM_SUBTREE_DEPTH = 5;
    public static final int NODE_TYPES = 7;
    private static final double NUM_BINARY_NODES = 5;

    public static FiringTree generateTree(int depth) {
	FiringTree tree = new FiringTree();
	tree.base = getWeightedNode(depth);
	return tree;
    }
    
    public static FiringTree generateTree() {
	return generateTree(DEFAULT_DEPTH);
    }
    
    /** Generates a slightly pre-planned tree with binary nodes at top, fire nodes at bottom */
    private static Node getWeightedNode(int depth) {
	Node node;
	if (depth < 0) {
	    return null;
	} else if (depth > DEFAULT_DEPTH - 3) { //use branching nodes at top of tree
	    int nodeChoice = (int) (Math.random() * 3); 
	    switch(nodeChoice){
		case 0: node = new EnergyBranch(); break;
		case 1: node = new FlipFlopBranch(); break;
		case 2: node = new PlayerLeftRightBranch();break;
		case 3: node = new PlayerUpDownBranch();break;
		default: node = new LastFiredBranch(); break;
	    }
	    } else {
		int nodeChoice = (int) (Math.random() * 2);
		switch(nodeChoice) {
		case 0: node = new ChangeAngleNode(); break;
		default: node = new FireNode();
		}
	}

	node.randomizeNode();
	if (node.isUnaryNode()) {
	    ((UnaryNode)node).setChild(getWeightedNode(depth - 1));
	} else {
	    ((BinaryNode)node).setLeftChild(getWeightedNode(depth - 1));
	    ((BinaryNode)node).setRightChild(getWeightedNode(depth - 1));
	}
	return node;
    }
    
    public static Node getRandomSubtree() {
	int depth = (int)(Math.random()*MAXIMUM_SUBTREE_DEPTH);
	//System.out.println("Generating subtree of depth " + depth);
	return getRandomSubtree(depth);
    }
    
    public static Node getRandomSubtree(int depth) {
	if (depth < 0) {
	    return null;
	}
	Node node;
	int nodeChoice = (int)(Math.random() * (NODE_TYPES));
	switch(nodeChoice) {
	case 0: node = new EnergyBranch();break;
	case 1: node = new FlipFlopBranch();break;
	case 2: node = new LastFiredBranch();break;
	case 3: node = new ChangeAngleNode();break;
	case 4: node = new PlayerLeftRightBranch();break;
	case 5: node = new PlayerUpDownBranch();break;
	default: node = new FireNode();

	}
	node.randomizeNode();
	if (node.isUnaryNode()) {
	    ((UnaryNode)node).setChild(getRandomSubtree(depth - 1));
	} else {
	    ((BinaryNode)node).setLeftChild(getRandomSubtree(depth - 1));
	    ((BinaryNode)node).setRightChild(getRandomSubtree(depth - 1));
	}
	return node;
    }
    public static BinaryNode getRandomBinaryNode() {
	int nodeChoice = (int)(Math.random()*NUM_BINARY_NODES);
	BinaryNode node;
	switch(nodeChoice) {
	case 0: 
	    node = new EnergyBranch();
	    break;
	case 1: 
	    node = new FlipFlopBranch();
	    break;
	case 2: 
	    node = new PlayerLeftRightBranch();
	    break;
	case 3: 
	    node = new PlayerUpDownBranch();
	    break;
	default:
	    node = new LastFiredBranch();
	    break;
	}
	return node;    
    }
    public UnaryNode getRandomUnaryNode() {
	if (Math.random() < .5) {
	    return new FireNode();
	} else {
	    return new ChangeAngleNode();
	}
    }
}
